# -*- coding: utf-8 -*-
# @Time : 2023/7/25 15:03
# @Author : cute
# @Email : Aggressive_cute@126.com

'''
target url:

https://passport.jd.com/new/login.aspx?ReturnUrl=https%3A%2F%2Fglobal.jd.com%2F
'''
import os
import copy
import hmac
import time
import hashlib
import requests
import binascii
from loguru import logger

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
from utils.tools import *

class Method:
    GET = 'GET'
    POST = 'POST'


def _request(method: str = 'GET', *args, **kwargs):
    """请求封装"""
    error_count = 0
    # 是否挂代理
    # if not self.proxies:
    #     headers, self.proxies = self.get_proxies(self.headers)
    while True:
        try:
            if error_count > 10:
                return

            if method == Method.GET:
                resp = requests.get(**kwargs)
            else:
                resp = requests.post(**kwargs)
            if resp.status_code in [403]:
                # logger.error(f'403 retry ...')
                continue
            return resp

        except Exception as e:
            logger.error('_request error {}'.format(e))
            error_count += 1

encrypt_ctx = execjs.compile(open(os.path.join(os.getcwd(), "views", "jd", "aes_4.7.4.js"), encoding='utf-8').read())
algo_encrypt_ctx = execjs.compile(
    open(os.path.join(os.getcwd(), "views", "jd", "algo_aes.js"), encoding='utf-8').read())


def get_distance(target_bytes, background_bytes):
    """
    计算滑块距离 (滑块/背景)
    :return: 左上角 右下脚 坐标
    """
    slide = ddddocr.DdddOcr(det=False, ocr=False, show_ad=False)
    res = slide.slide_match(target_bytes, background_bytes, simple_target=True)
    distance = res["target"][0]
    return distance


class JdSlide():

    def __init__(self, appId, e):
        self.session = requests.session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36",
        })
        self.appId = appId
        self.e = e
        self.image_dir = "images"
        if not os.path.exists(self.image_dir):
            os.makedirs(self.image_dir)

    def download_image(self, base64_string, file_path):
        """
        图片存储,后续识别
        :param base64_string: 字符串
        :param file_path: 文件地址
        :return:
        """
        bs = base64.b64decode(base64_string)
        with open(file_path, mode='wb') as f:
            f.write(bs)
            # print("图片:{}, 下载完成...".format(file_path))

    def do_jd(self):
        """
        第一次请求
        :return:
        """
        params = {
            "appId": self.appId,
            "scene": "login",
            "product": "click-bind-suspend",
            # 浏览器指纹 算出来的 无痕浏览器可以查看
            "e": self.e,
            "j": "",
            "lang": "zh_CN",
            "callback": ""  # 技巧 置空
        }
        response = self.session.get("https://iv.jd.com/slide/g.html?", params=params)

        js_data = response.json()
        # 滑块
        challenge = js_data["challenge"]
        # 滑块图
        patch = js_data["patch"]
        # 背景图
        bg = js_data["bg"]
        # 识别滑块距离
        target_bs = base64.b64decode(patch)
        bg_bs = base64.b64decode(bg)
        distance = get_distance(target_bs, bg_bs)
        # 可定会有一定的误差: 时间 滑块识别位置 算法 延时操作
        # 坑点: 比较页面（278 × 108 ） 等比缩放即可 360 * 140 滑动查看一开始的距离 就滑一点 滑块是页面坐标开始的(经验)
        distance = int(0.7722222222222222 * distance + 23.4)
        # 生成轨迹，加密数据
        # web服务器
        script_path = os.path.join(os.getcwd(), "views", "jd", "slider.js")

        # 脚本一键测试
        # script_path = './slider.js'
        obj = execjs.compile(open(script_path, encoding="utf-8").read())
        d = obj.call("getSlide", distance)
        # 延时等待
        time.sleep(8)
        params = {
            "d": d,  # 轨迹算法
            "c": challenge,
            "w": "278",
            "appId": self.appId,
            "scene": "login",
            "product": "click-bind-suspend",
            "e": self.e,
            "j": "",
            "o1": "0",
            "u": "https://passport.jd.com/new/login.aspx?ReturnUrl=https%3A%2F%2Fwww.jd.com%2F",
            "lang": "zh_CN",
            "callback": ""
        }

        response = self.session.get("https://iv.jd.com/slide/s.html", params=params)
        return response.json()


class Jdlogin():

    def __init__(self):
        script_path = os.path.join(os.getcwd(), "views", "jd", "getAksParams.js")
        js_code = open(script_path, mode='r', encoding='utf-8').read()
        self.ctx = execjs.compile(js_code)

def get_h5stv4_7_4(params, global_this):
    """h5st v4.7.4"""
    import datetime
    def get_env_str():
        """各种指纹+AES-CBC 验证ok 魔改版本"""
        random_val = ''.join(random.choices("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-", k=12))
        plaintext_dict = {
            "sua": global_this['sua'],
            "pp": {
                "p2": global_this['pin']
            },
            "extend": {
                "wd": 0,
                "l": 0,
                "ls": 5,
                "wk": 0,
                "bu1": "0.1.4",
                "bu2": -1,
                "bu3": 19,
                "bu4": 0,
                "bu5": 0,
                "bu6": 15
            },
            "random": random_val,
            "v": global_this['version'],
            "fp": global_this['_fingerprint'],
        }
        plaintext = json.dumps(plaintext_dict, indent=2, ensure_ascii=False)
        return encrypt_ctx.call('getLastEncryptStr', *(plaintext, global_this['key'], global_this['iv']))

    def my_md5(encrypt_data) -> str:
        """
        md5摘要算法加密
        :return:
        """
        from hashlib import md5
        obj = md5()
        obj.update(encrypt_data.encode('utf-8'))
        res = obj.hexdigest()
        return res

    def my_sha256(encrypt_data, ) -> str:
        """
        md5摘要算法加密
        :return:
        """
        from hashlib import sha256
        obj = sha256()
        obj.update(encrypt_data.encode('utf-8'))
        res = obj.hexdigest()
        return res

    def my_sha512(encrypt_data) -> str:
        """
        md5摘要算法加密
        :return:
        """
        from hashlib import sha512
        obj = sha512()
        obj.update(encrypt_data.encode('utf-8'))
        res = obj.hexdigest()
        return res

    def hmac_sha512(key, message):
        hmac_digest = hmac.new(key.encode('utf-8'), message.encode('utf-8'), hashlib.sha512).hexdigest()
        return hmac_digest

    def hmac_sha256(key, message):
        hmac_digest = hmac.new(key.encode('utf-8'), message.encode('utf-8'), hashlib.sha256).hexdigest()

        return hmac_digest

    def hmac_md5(key, message):
        hmac_digest = hmac.new(key.encode('utf-8'), message.encode('utf-8'), hashlib.md5).hexdigest()
        return hmac_digest

    def bxdkEqk_key(hmac_key):
        """hmac魔改key操作"""
        first_key_list = list(hmac_key)
        first_key_list[2] = chr(158 - ord(first_key_list[2]))
        first_key_list[0], first_key_list[4] = chr(158 - ord(first_key_list[4])), chr(158 - ord(first_key_list[0]))
        first_key_list[1], first_key_list[3] = chr(158 - ord(first_key_list[3])), chr(158 - ord(first_key_list[1]))
        return ''.join(first_key_list)

    salt = '7n5<G*'
    algo_type = global_this['algo_type']
    algo_mapping = {
        'MD5': my_md5,
        'SHA256': my_sha256,
        'SHA512': my_sha512,
        'HmacMD5': hmac_md5,
        'HmacSHA256': hmac_sha256,
        'HmacSHA512': hmac_sha512,
    }
    sorted_params = dict(sorted(params.items(), key=lambda x: x[0]))
    sorted_params['body'] = my_sha256(sorted_params['body'])
    timestamp = int(time.time() * 1e3)
    env_str = get_env_str()
    dt_object = datetime.datetime.fromtimestamp(timestamp // 1000) + datetime.timedelta(milliseconds=timestamp % 1000)
    formatted_time = dt_object.strftime('%Y%m%d%H%M%S') + str(dt_object.microsecond // 1000)[:3]

    data_str = f"{global_this['_token']}{global_this['_fingerprint']}{formatted_time + '47'}{global_this['_appId']}{global_this['rd']}"

    if 'Hmac' in algo_type:
        hmac_key = bxdkEqk_key(global_this['_token'])
        first_key = algo_mapping[algo_type](hmac_key, f'{data_str}{salt}')
    else:
        first_key = algo_mapping[algo_type](f'{data_str}{salt}')

    mid_str = '&'.join([f'{key}:{sorted_params[key]}' for key in sorted_params])

    second_key = my_md5(f'{first_key}{mid_str}{first_key}{salt}')

    # 新更新签名
    # appid functionId
    new_sorted_params = {
        'appid': sorted_params['appid'],
        'functionId': sorted_params['functionId'],
    }
    # logger.info(f'sorted_params: {sorted_params}')
    sign_mid_str = '&'.join([f'{key}:{new_sorted_params[key]}' for key in new_sorted_params])
    sign_str = my_md5(f'{first_key}{sign_mid_str}{first_key}{salt}')

    h5st = ';'.join(
        [
            formatted_time,
            global_this['_fingerprint'],
            global_this['_appId'],
            global_this['_token'],
            second_key,
            global_this['_version'],
            str(timestamp),
            env_str,
            sign_str
        ]
    )
    return h5st

def get_fp_4_7_4():
    '''
    4.7.4fp纯算
    :return:
    '''
    import random
    all_s = "1uct6d0jhq"

    def to_base36(num):
        digits = "0123456789abcdefghijklmnopqrstuvwxyz"
        return digits[num]

    s = all_s
    n = []
    a = 10
    t = 5
    for i in range(len(s)):
        if random.random() * a < t:
            n.append(s[i])
            t -= 1
            if t == 0:
                break
        a -= 1
    middle_s = ""
    for c in range(len(n)):
        u = int(random.random() * (len(n) - c))
        middle_s += n[u]
        n[u] = n[len(n) - c - 1]
    new_s = "".join(z for z in s if z not in middle_s)
    left_int = random.randint(1, 9)
    pre_s = "".join(random.choice(new_s) for _ in range(left_int))
    cur_s = pre_s + middle_s
    follow_s = "".join(random.choice(new_s) for _ in range(10 - left_int))
    merge_s = cur_s + follow_s + str(left_int)
    ans = []
    for i in range(len(merge_s)):
        cur_index = len(merge_s) - i - 1
        if i == 0:
            ans.append(merge_s[cur_index])
        else:
            z = to_base36(35 - int(merge_s[cur_index], 36))
            ans.append(z)
    ans = "".join(ans[1:] + ans[:1])
    return ans



def update_algo_4_7_4_v2(app_id, ua, cookies, global_this, version='4.7', platform='web',fp=get_fp_4_7_4(), pf="Win32",proxies={}):
    """算法自动更新"""

    def get_algo_env_str(algo_global_this):
        """各种指纹+AES-CBC 验证ok 魔改版本"""
        # 指纹信息
        random_val = ''.join(random.choices("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-", k=12))
        plaintext_dict = {
            "wc": 0,
            "wd": 0,
            "l": "zh-CN",
            "ls": "zh-CN,zh",
            "ml": 2,
            "pl": 5,
            "av": algo_global_this['av'],
            "ua": algo_global_this['ua'],
            "sua": algo_global_this['sua'],
            "pp": {
                "p2": global_this['pin']
            },
            "extend": {
                "wd": 0,
                "l": 0,
                "ls": 5,
                "wk": 0,
                "bu1": "0.1.4",
                "bu2": 0,
                "bu3": 20,
                "bu4": 0,
                "bu5": 0,
                "bu6": 15
            },
            "pp1": "",
            "w": 1920,
            "h": 1080,
            "ow": 1918,
            "oh": 1048,
            "url": "https://cart.jd.com/addToCart.html?rcd=1&pid=100071377749&pc=1&eb=1&rid=1722608119757&em=",
            "og": "https://cart.jd.com",
            "pf": "Win32",
            "pr": 1,
            "re": "https://item.jd.com/",
            "random": random_val,
            "referer": "https://item.jd.com/",
            "v": global_this['version'],
            "bu2": "    at https://storage.360buyimg.com/webcontainer/js_security_v3_0.1.4.js:3:7011",
            "canvas": algo_global_this['canvas'],
            "webglFp": algo_global_this['webglFp'],
            "ccn": 12,
            "ai": algo_global_this['_appId'],
            "fp": algo_global_this['_fingerprint']
        }
        plaintext = json.dumps(plaintext_dict, indent=2, ensure_ascii=False)
        return algo_encrypt_ctx.call('getLastEncryptStr', *(plaintext, algo_global_this['key'], algo_global_this['iv']))

    headers = {
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Origin": "https://cart.jd.com",
        "Pragma": "no-cache",
        "Referer": "https://cart.jd.com/",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-site",
        "User-Agent": ua,
        "accept": "application/json",
        "content-type": "application/json",
        "sec-ch-ua": "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }
    rd_obj = re.compile("{var rd='(.*?)';var", re.S)
    algo_obj = re.compile("return algo.(.*?)\(", re.S)
    fv = global_this['version']
    algo_global_this = {
        "_version": version,
        "version": fv,
        "_appId": app_id,
        "_fingerprint": fp,
        'sua': re.search(r"\((?P<sua>.*?)\)", ua).group('sua'),
        "canvas": global_this['canvas'],
        "webglFp": global_this['webglFp'],
        'ua': ua,
        'av': ua.replace('Mozilla/', ''),
        'pin': cookies.get('pin', cookies.get('pt_pin')),
        'iv': '0102030405060708',
        'key': 'wm0!@w-s#ll1flo(',
    }
    expandParams = get_algo_env_str(algo_global_this)
    timestamp = int(time.time() * 1e3)
    data = {
        "appId": app_id,
        "expandParams": expandParams,
        "fp": fp,
        "fv": fv,
        "platform": platform,
        "timestamp": timestamp,
        "version": version,
    }
    # data = json.dumps(data, separators=(',', ':'))
    algo_url = "https://cactus.jd.com/request_algo"
    spider_config = {
        'url': algo_url,
        'json': data,
        'proxies': proxies,
        'headers': headers,
        'timeout': 5,
    }
    response = _request(
        method='POST',
        **spider_config
    )
    data_dict = response.json()
    if data_dict['status'] != 200:
        raise Exception('request_algo 算法获取失败 ！')

    tk = data_dict['data']['result']['tk']
    fp = data_dict['data']['result']['fp']
    algo = data_dict['data']['result']['algo']
    rd = rd_obj.search(algo).group(1)
    algo_type = algo_obj.search(algo).group(1)
    return {
        '_token': tk,
        'rd': rd,
        '_fingerprint': fp,
        'algo_type': algo_type,
    }


def update_algo_4_7_4(app_id, ua, cookies,global_this, version='4.7', platform='web', fp=get_fp_4_7_4(),pf="Win32"):
    """算法自动更新"""

    def get_algo_env_str():
        """各种指纹+AES-CBC 验证ok 魔改版本"""
        # 指纹信息
        random_val = ''.join(random.choices("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-", k=11))
        plaintext_dict = {
            "wc": 0,
            "wd": 0,
            "l": "zh-CN",
            "ls": "zh-CN,zh",
            "ml": 2,
            "pl": 5,
            "av": algo_global_this['av'],
            "ua": algo_global_this['ua'],
            "sua": algo_global_this['sua'],
            "pp": {
                "p2": algo_global_this['pin']
            },
            "extend": {
                "wd": 0,
                "l": 0,
                "ls": 5,
                "wk": 0,
                "bu1": "0.1.9",
                "bu2": 0,
                "bu3": 87,
                "bu4": 0,
                "bu5": 0,
                "bu6": 40,
                "bu7": "",
                "bu8": 0
            },
            "pp1": "",
            "bu1": "",
            "w": 1920,
            "h": 1080,
            "ow": 1918,
            "oh": 1048,
            "url": "https://item.jd.com/100071377749.html",
            "og": "https://item.jd.com",
            "pf": pf,
            "pr": 1,
            "re": "",
            "random": random_val,
            "referer": "",
            "v": global_this['version'],
            "bu2": "    at https://storage.360buyimg.com/webcontainer/main/js_security_v3_main_0.1.8.js?v=20240726:3:7011",
            "canvas": global_this['canvas'],
            "canvas1": global_this['canvas'],
            "webglFp": global_this['webglFp'],
            "webglFp1": global_this['webglFp'],
            "ccn": 12,
            "ai": global_this['_appId'],
            "fp": global_this.get('_fingerprint',fp)
        }

        plaintext_dict = {
            "wc": 0,
            "wd": 0,
            "l": "zh-CN",
            "ls": "zh-CN,zh",
            "ml": 2,
            "pl": 5,
            "av": algo_global_this['av'],
            "ua": algo_global_this['ua'],
            "sua": algo_global_this['sua'],
            "pp": {
                "p2": global_this['pin']
            },
            "extend": {
                "wd": 0,
                "l": 0,
                "ls": 5,
                "wk": 0,
                "bu1": "0.1.4",
                "bu2": 0,
                "bu3": 18,
                "bu4": 0,
                "bu5": 0,
                "bu6": 15
            },
            "pp1": "",
            "w": 1512,
            "h": 982,
            "ow": 1512,
            "oh": 944,
            "url": "https://cart.jd.com/addToCart.html?rcd=1&pid=6101224&pc=1&eb=1&rid=1722092300143&em=",
            "og": "https://cart.jd.com",
            "pf": "MacIntel",
            "pr": 2,
            "re": "https://cfe.m.jd.com/",
            "random": random_val,
            "referer": "https://cfe.m.jd.com/",
            "v": global_this['version'],
            "bu2": "    at https://storage.360buyimg.com/webcontainer/js_security_v3_0.1.4.js:3:7011",
            "webglFp": global_this['webglFp'],
            "webglFp1": global_this['webglFp'],
            "ccn": 11,
            "ai": global_this['_appId'],
            "fp": global_this.get('_fingerprint', fp)
        }

        plaintext = json.dumps(plaintext_dict, indent=2, ensure_ascii=False)
        return algo_encrypt_ctx.call('getLastEncryptStr', *(plaintext, algo_global_this['key'], algo_global_this['iv']))

    proxies = {}
    headers = {
        "Accept-Language": "zh-CN,zh;q=0.9",
        "Cache-Control": "no-cache",
        "Connection": "keep-alive",
        "Origin": "https://item.jd.com",
        "Pragma": "no-cache",
        "Referer": "https://item.jd.com/",
        "Sec-Fetch-Dest": "empty",
        "Sec-Fetch-Mode": "cors",
        "Sec-Fetch-Site": "same-site",
        "User-Agent": ua,
        "accept": "application/json",
        "content-type": "application/json",
        "sec-ch-ua": "\"Not/A)Brand\";v=\"8\", \"Chromium\";v=\"126\", \"Google Chrome\";v=\"126\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\""
    }
    rd_obj = re.compile("{var rd='(.*?)';var", re.S)
    algo_obj = re.compile("return algo.(.*?)\(", re.S)
    fv = global_this['version']
    algo_global_this = {
        "_version": version,
        "version": fv,
        "_appId": app_id,
        "_fingerprint": fp,
        'sua': re.search(r"\((?P<sua>.*?)\)", ua).group('sua'),
        "canvas": global_this['canvas'],
        "webglFp": global_this['webglFp'],
        'ua': ua,
        'av': ua.replace('Mozilla/', ''),
        'pin': cookies.get('pin', cookies.get('pt_pin')),
        'iv': '0102030405060708',
        'key': 'wm0!@w-s#ll1flo(',
    }
    expandParams = get_algo_env_str()
    timestamp = int(time.time() * 1000)
    data = {
        "version": version,
        "fp": fp,
        "appId": app_id,
        "timestamp": timestamp,
        "platform": platform,
        "expandParams": expandParams,
        "fv": fv
    }
    data = json.dumps(data, separators=(',', ':'))
    algo_url = "https://cactus.jd.com/request_algo"
    spider_config = {
        'url': algo_url,
        'data': data,
        'proxies': proxies,
        'headers': headers,
        'timeout': 5,
    }
    response = _request(
        method='POST',
        **spider_config
    )
    data_dict = response.json()
    if data_dict['status'] != 200:
        raise Exception('request_algo 算法获取失败 ！')

    tk = data_dict['data']['result']['tk']
    fp = data_dict['data']['result']['fp']
    algo = data_dict['data']['result']['algo']
    rd = rd_obj.search(algo).group(1)
    algo_type = algo_obj.search(algo).group(1)
    return {
        '_token': tk,
        'rd': rd,
        '_fingerprint': fp,
        'algo_type': algo_type,
    }


if __name__ == '__main__':
    params = {
        "appid": "pc-item-soa",
        "body": dumps(
            {"skuId": 100078020150, "cat": "9987,653,655", "area": "15_1213_3038_59931", "shopId": "1000004123",
             "venderId": 1000004123,
             "paramJson": dumps(
                 {"platform2": "1", "specialAttrStr": "p0pppppppp2pp1p1pppppppppppppp", "skuMarkStr": "00"}),
             "num": 1,
             "bbTraffic": "",
             "canvasType": 1}),
        "client": "pc",
        "clientVersion": "1.0.0",
        "functionId": "pc_detailpage_wareBusiness",
        "t": int(time.time() * 1e3),
    }
    global_this = {'_version': '4.7', 'version': 'h5_file_v4.7.4', '_appId': '4b6ff',
                   '_fingerprint': '6yy5nng9imz66yy4', 'canvas': '1ea63d9194e38ba1f3507fef6d45ce33',
                   'webglFp': 'b2bffcfae898a7d97b93b3174a5c891a', 'sua': 'Windows NT 10.0; Win64; x64',
                   'iv': '0102030405060708', 'key': '_M6Y?dvfN40VMF[X', 'pin': 'cute_test',
                   '_token': 'tk03w97dc1ba118niwTvT5cBDp0KCiiIKEdyZGJWe4DV4rIBSFihJAy5JfB_7BnRF-pqob_EYfcen2r-YWOte4VGJ2In',
                   'rd': 'XcUXEh07t9Pk', 'algo_type': 'HmacSHA512'}
    h5st = get_h5stv4_7_4(params, global_this)
    print(h5st)
